
#include <CommLib.hpp>
#include <GfxLib.hpp>
#include <MathLib.hpp>
#include <MeshLib.hpp>

using namespace CommLib;
using namespace GfxLib;
using namespace MathLib;
using namespace MeshLib;

#include "Camera.hpp"
#include "ProceduralFishes.hpp"
#include "EmitterBase.hpp"

// =========================================================
// Global/Local Data
// =========================================================

float lastTime = 0;
float currentTime = 0;
float deltaTime = 0;

const int numLights = 2;
Vec3f lightPositions[numLights] = { Vec3f(-50, 30, 10), Vec3f(50, 30, 10) };

int font = 0;
FlockOfFishes * flock = 0;

// 3D First Person Camera
Camera camera;

// For bubbles
// Global list of particles
std::list<ParticleBase *> particles;

// Emitter
EmitterBase * emitter = 0;

// Bubbles
PixelBuffer bubble;

// Amount to move camera via keyboard and mouse input.
float camMoveSpeed(70.0f * (1.0f / 60.0f));
float camRotateSpeed(5.0f * (1.0f / 60.0f));

// =========================================================
// Routines
// =========================================================

inline int KeyDown(int key)
{
	return ((GetAsyncKeyState(key) & 0x8000) ? 1 : 0);
}

void Begin2D(void)
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();

	glLoadIdentity();

	// Set 2D projection:
	glOrtho(0.0, static_cast<double>(screen.width), static_cast<double>(screen.height), 0.0, -1.0, 1.0);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// Prepare OpenGL to draw sprites with alpha:
	glPushAttrib(GL_ALL_ATTRIB_BITS);

	glDisable(GL_TEXTURE_2D);
	glEnable(GL_COLOR_MATERIAL);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER, 0);
}

void End2D(void)
{
	glPopAttrib();

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
}

void CameraKeyboardInput(void)
{
	// Standard FPS Controls //

	if (KeyDown('W') || KeyDown(VK_UP))
	{
		camera.Move(Camera::FORWARD, camMoveSpeed, 1.0f, 1.0f, 1.0f);
	}
	if (KeyDown('S') || KeyDown(VK_DOWN))
	{
		camera.Move(Camera::BACK, camMoveSpeed, 1.0f, 1.0f, 1.0f);
	}
	if (KeyDown('D') || KeyDown(VK_RIGHT))
	{
		camera.Move(Camera::RIGHT, camMoveSpeed, 1.0f, 1.0f, 1.0f);
	}
	if (KeyDown('A') || KeyDown(VK_LEFT))
	{
		camera.Move(Camera::LEFT, camMoveSpeed, 1.0f, 1.0f, 1.0f);
	}
}

void CameraMouseInput(void)
{
	// NOTE: Windows dependant!

	static const float maxAngle = 89.5f; // Max degrees of rotation.
	static float pitchAmt = 0.0f;

	// Get the middle of the window
	const int midScrX = screen.width  >> 1;
	const int midScrY = screen.height >> 1;

	float amt;
	POINT pt = {0};
	GetCursorPos(&pt); // Get the current mouse position

	// Rotate left/right
	amt = static_cast<float>(midScrX - pt.x) * camRotateSpeed;
	camera.Rotate(amt * MathLib::DEG_TO_RAD);

	// Calculate amount to rotate up/down
	amt = static_cast<float>(midScrY - pt.y) * camRotateSpeed;

	// Clamp pitch amount
	if ((pitchAmt + amt) <= -maxAngle)
	{
		amt = -maxAngle - pitchAmt;
		pitchAmt = -maxAngle;
	}
	else if ((pitchAmt + amt) >= maxAngle)
	{
		amt = maxAngle - pitchAmt;
		pitchAmt = maxAngle;
	}
	else
	{
		pitchAmt += amt;
	}

	// Pitch camera
	camera.Pitch(amt * MathLib::DEG_TO_RAD);

	// Set our cursor back to the middle of the screen to avoid problems
	SetCursorPos(midScrX, midScrY);
}

void Render(void)
{
	// Update deltaTime:
	lastTime = currentTime;
	currentTime = GetElapsedSeconds();
	deltaTime = (currentTime - lastTime);

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	// Update Camera:
	CameraKeyboardInput();
	CameraMouseInput();

	const Vec3f up(camera.GetUp());
	const Vec3f eye(camera.GetEye());
	const Vec3f target(camera.GetTarget());

	gluLookAt(eye.x, eye.y, eye.z, target.x, target.y, target.z, up.x, up.y, up.z);

	// Render meshes
	flock->UpdateFishes(deltaTime);
	flock->RenderFishes(deltaTime);

	// Some text drawing...
	SetTextColor(PackRGB(220, 220, 220));
	ScreenPrintf(font, 10, 10, "Running @ %d FPS", CalcFPS());

	// Update emitter
	emitter->Update();

	// Update all the particles and check if they need to be deleted
	std::list<ParticleBase*>::iterator it;
	std::list<ParticleBase*>::iterator toDelete[12]; // Lets just pretend that no more than 12 bubbles will die in one frame
	int t_counter = 0;

	// 2D drawing:

	Begin2D();

	for (it = particles.begin(); it != particles.end(); it++)
	{
		(*it)->Update(deltaTime);
		if ((*it)->GetLife() < 0)
		{
			// Put on dead list
			if (t_counter < 12)
			{
				toDelete[t_counter] = it;
				t_counter++;
			}
		}
	}

	End2D();

	// Delete dead particles
	for (int j = 0; j < t_counter; j++)
	{
		delete *toDelete[j];
		particles.erase(toDelete[j]);
	}

	glutSwapBuffers();
}

void ReshapeWindow(int w, int h)
{
	SetViewPort(w, h, fov, zNear, zFar);
}

void Keyboard(unsigned char key, int x, int y)
{
	switch (key)
	{
	case 27: // ESCAPE Key.
		exit(0);
		break;
	} // End switch (key)
}

void Cleanup(void)
{
	// Free memory like a boss :-P
	delete flock;
	delete emitter;
	free(bubble.pixels);

	// Close the GFX module
	GfxLib::Terminate();
}

int main(void)
{
	CommLib::verbosityLevel = VL_PRINT_ALL;

	// Init GFX library
	GfxLib::Initialize("UniLibrary/GfxLib/GFXConfig.ini");
	GfxLib::EnableLights(numLights, lightPositions);
	GfxLib::SetCallbacks(Render, ReshapeWindow, 0, 0, Keyboard, 0);

	// Load resources/assets
	font = CreateBitmapFont("Courier New", 0, 24, true, false, false, false);

	// Create the procedural fishes
	flock = new FlockOfFishes("ProceduralFish.ini");

	// Load bubble image
	bubble.pixels = GfxLib::LoadTgaImage("bubble.tga", bubble.width, bubble.height, bubble.bytesPerPix);
	assert(bubble.pixels != 0);

	// Create emiiter for particles
	emitter = new EmitterBase(&particles, 100, screen.height, &bubble);

	// Call Cleanup() at exit
	atexit(Cleanup);

	// Run the Render() function in an infinite loop. Call exit() to break it
	GfxLib::RunRenderingLoop();

	return (0);
}

/*
	Some random programming links:

	OBJ full spec:
	http://paulbourke.net/dataformats/obj/

	Some shaders:
	http://www.koders.com/noncode/fidABFE717EBD1DACD966EB8AFA7C5AA7098EA2ADA4.aspx

	GL VBOs:
	http://www.songho.ca/opengl/gl_vbo.html
	http://nehe.gamedev.net/tutorial/vertex_buffer_objects/22002/
	http://www.gamedev.net/topic/336689-vbos/
	http://www.opengl.org/wiki/VBO_-_just_examples
*/